自作アプリからSNSアプリへ画像を共有する最適なフォーマットは?UIImage、Data、URL の比較調査した

自作アプリからSNSアプリへ画像を共有する最適なフォーマットは?UIImage、Data、URL の比較調査した

Clock Icon2024.07.24

一般的にアプリ間連携はUIActivityViewControllerを利用して行われるが、自作のiOS アプリから画像や動画を共有しても、受信側のアプリがそれを正しく受け取るかどうかはわからない。画像または動画を共有する際には、受信側(共有先)のアプリが当該のフォーマットに対応しているかを調査した。

調査の背景

2週間前にiPhoneからTwitter(現X)に画像と動画を共有するアプリを公開した。ありがたいことにリリース直後から好評で、ユーザーが増えるにつれて、MastodonやBlueskyへの投稿を望む声が増えた。

その時点では、Twitterへの画像共有は、画像のファイルパスを示したURLを渡していたが、Blueskyアプリに対して、この方法で画像を共有すると、文字列としてファイルパスが表示されてしまうことがわかった。

特定のアプリに対して、どのフォーマットで画像を渡せばよいか調査を行うことにした。

iOS アプリから各SNSアプリへ画像を共有するのに最適なフォーマットはなにか

各アプリでの動作確認結果を以下にまとめた。私のアプリにて利用している「画像とテキスト(ハッシュタグ)」または「動画とテキスト(ハッシュタグ)」の組み合わせで調査した。

検証環境

  • iOS 17.5

カメラロールへの保存

以下の動作確認を行った。

メディアのPlaceholder アイテム テキストのPlaceholder アイテム 結果 備考
画像+テキスト URL URL String String o
画像+テキスト UIImage UIImage String String o
画像+テキスト UIImage URL String String o
動画+テキスト URL URL String String o

カメラロールへの保存なのでテキストデータは無視されることを確認した。また他のアプリでは正常に動作しない 動画+画像+テキスト のケースでも正しくデータが処理されることを確認している。

どんなフォーマットでも処理できるのは、さすが標準アプリたる所以である。

Twitter(現X)への共有

X v10.50 (12) にて、以下の動作確認をおこなった。

メディアのPlaceholder アイテム テキストのPlaceholder アイテム 結果 備考
画像+テキスト URL URL String String o
画像+テキスト UIImage UIImage String String o
画像+テキスト UIImage URL String String o
動画+テキスト URL URL String String o

Twitterへの共有では、動画+画像+テキスト の場合には静止画が無視されてしまうようだ。注意したい。

Blueskyへの共有

Bluesky v1.88.0.366 にて、以下の動作確認を行った。

メディアのPlaceholder アイテム テキストのPlaceholder アイテム 結果 備考
画像+テキスト URL URL String String x 画像のファイルパスが表示されてしまう
画像+テキスト UIImage UIImage String String x BlueSkyアプリがクラッシュする
画像+テキスト UIImage UIImage String nil o テキスト表示されない
画像+テキスト UIImage URL String String x BlueSkyアプリがクラッシュする
画像+テキスト UIImage URL String nil x 画像のファイルパスが表示されてしまう
動画+テキスト URL URL String String x 動画の場合は候補に出てこない
動画+テキスト URL URL String nil x 動画の場合は候補に出てこない

Blueskyアプリ側の問題だと思うが、画像とテキスト(ハッシュタグ)の両方を同時に共有することができない。エラーダイアログや、UIActivityViewController からのエラーが返ってこずにBlueskyの共有エクステンションがクラッシュしたような挙動となる。

20240722221255

Mastodonへの共有

Mastodon for iOS v2024.7 (6466) にて、以下の動作確認を行った。

メディアのPlaceholder アイテム テキストのPlaceholder アイテム 結果 備考
画像+テキスト URL URL String String 画像は表示されるが、画像のファイルパスも表示されてしまう
画像+テキスト UIImage UIImage String String x 画像が表示されない
画像+テキスト UIImage URL String String 画像は表示されるが、画像のファイルパスも表示されてしまう
画像+テキスト UIImage Data String String o 画像が表示される
動画+テキスト URL URL String String 画像は表示されるが、画像のファイルパスも表示されてしまう

Mastodonアプリは、UIImageを渡すことができず、ファイルパスのURLにて共有する必要があるが、URLを共有するとファイルパスを表示してしまう問題がある。PlaceholderにUIImageを設定しておき、アイテムとしてDataを渡すと問題なく画像が表示される。

20240722221324

Threadsへの共有

Threads v340.0 にて、以下の動作確認を行った。

メディアのPlaceholder アイテム テキストのPlaceholder アイテム 結果 備考
画像+テキスト URL URL String String x 画像のファイルパスが表示されてしまう
画像+テキスト UIImage UIImage String String テキストが表示されない
画像+テキスト UIImage URL String String x 画像のファイルパスが表示されてしまう
動画+テキスト URL URL String String x 動画が無視される

Threadsアプリに画像とテキストを同時に共有した場合、テキストが無視されてしまう。また動画の共有ができない。

Instagramへの共有

Instagram v340.0 にて、以下の動作確認を行った。

メディアのPlaceholder アイテム テキストのPlaceholder アイテム 結果 備考
画像+テキスト URL URL String String x エラーダイアログが表示される
画像+テキスト UIImage UIImage String String x エラーダイアログが表示される
画像+テキスト UIImage URL String String x エラーダイアログが表示される
画像+テキスト UIImage URL String nil o
動画+テキスト URL URL String String x エラーダイアログが表示される
動画+テキスト URL URL String nil o

画像とテキストを共有しようとすると「複数の写真と動画をシェアできるのは Instagram アプリからのみです。」とエラーダイアログが表示される。

20240722221220

各SNSアプリに対して適切なフォーマットで送るためにはどうすればよいか

執筆時点での他アプリへの共有時の挙動について調査した。

それぞれのSNSアプリに対応できるように、以下のように共有先に応じて activityViewController(_:, itemForActivityType:) で返す型を変える ImageFileActivityItem を実装した。

import LinkPresentation
import UIKit
import UniformTypeIdentifiers

extension UIActivity.ActivityType {
    static let bluesky = UIActivity.ActivityType(rawValue: "xyz.blueskyweb.app.Share-with-Bluesky")
    static let threads = UIActivity.ActivityType(rawValue: "com.burbn.barcelona.ShareExtension")
    static let instagram1 = UIActivity.ActivityType(rawValue: "com.burbn.instagram.shareextension")
    static let instagram2 = UIActivity.ActivityType(rawValue: "com.instagram.shareextension")
    static let instagram3 = UIActivity.ActivityType(rawValue: "com.instagram.exclusivegram")
    static let mastdon = UIActivity.ActivityType(rawValue: "org.joinmastodon.app.ShareActionExtension")
}

class ImageFileActivityItem: NSObject, UIActivityItemSource {
    let fileUrl: URL
    let fileName: String
    let image: UIImage?

    init(fileUrl: URL, image: UIImage?) {
        self.fileUrl = fileUrl
        self.image = image
        fileName = fileUrl.lastPathComponent
    }

    func activityViewControllerPlaceholderItem(_: UIActivityViewController) -> Any {
        // Bluesky へ共有するためにプレースホルダーにUIImageを指定する必要がある
        image ?? UIImage()
    }

    func activityViewController(_: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        return switch activityType {
        case .bluesky, .threads:
            // Blueskyアプリは URL を渡しても処理ができない
            image

        case .mastdon:
            // MastdonアプリにURLを渡すと画像+ファイルパスが共有され、
            // UIImage を渡すとアップロードエラーが発生するため、Data を渡す
            try? Data(contentsOf: fileUrl)

        default:
            fileUrl
        }
    }

    func activityViewController(_: UIActivityViewController, subjectForActivityType _: UIActivity.ActivityType?) -> String {
        fileName
    }

    func activityViewController(_: UIActivityViewController, dataTypeIdentifierForActivityType _: UIActivity.ActivityType?) -> String {
        UTType.image.identifier
    }

    func activityViewControllerLinkMetadata(_: UIActivityViewController) -> LPLinkMetadata? {
        guard let image = image else { return nil }

        let metadata = LPLinkMetadata()
        metadata.title = fileName
        metadata.imageProvider = NSItemProvider(object: image)
        return metadata
    }
}

class HashTagActivityItem: NSObject, UIActivityItemSource {
    let hashTag: String

    init(hashTag: String) {
        self.hashTag = hashTag
    }

    func activityViewControllerPlaceholderItem(_: UIActivityViewController) -> Any {
        return hashTag
    }

    func activityViewController(_: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        return switch activityType {
        case .bluesky:
            // Bluesky へ共有する時には画像とテキストを両方投稿することができないためハッシュタグを空で送る
            nil

        case .instagram1, .instagram2, .instagram3:
            // Instagram へ共有する時には画像とテキストを両方投稿することができないためハッシュタグを空で送る
            nil

        default:
            hashTag
        }
    }
}

まとめ

どのようなフォーマットでもTwitterアプリは正しく受け取ってくれる。その反面、Twitter以外のアプリでは対応しているフォーマットがまちまちであった。

本記事では、UIActivityItemSource protocolを実装した ImageFileActivityItem を使い、activityViewController(_:, itemForActivityType:)のタイミングで、適切なフォーマットに変換した上でデータを渡す方法を紹介した。

参考

この記事をシェアする

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.